import { EGrooveGeometryType, IPlank, PointObj3 } from '@/types';
import { ProForm, useStyle } from '@ant-design/pro-components';
import { ConfigProvider, Divider, GetProp, TabsProps, message } from 'antd';
import {
  camelCase,
  chunk,
  cloneDeep,
  defaults,
  groupBy,
  isEmpty,
  pick,
  transform,
  values,
} from 'lodash-es';
import {
  useContext,
  useEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from 'react';
import { isRectPlank } from '../Plank/PlankBaseInfo';
import { Basis } from './constants';
import styles from './index.less';
import {
  HoleEditor,
  LimitHeight,
  PlankEditor,
  RoundCornerGrooveEditor,
  SquareGrooveEditor,
} from './modules';
import { Block, BlockDivider, EdgeInfo, PlankInfo } from './modules/PlankInfo';
import { EditType, FormValue } from './types';
import { getEndPointByFace } from './utils';
import { getEditTypes } from './utils/editor';
import { formHasError, handleLengthChange } from './utils/form';
import { getGroupedGrooveInitValue } from './utils/groove';
import { IconFont, IconType } from '../AntdIcon';
import { Matrix3, Matrix4, Vector2, Vector3 } from 'three';
import { Number3 } from '@/utils';
import classNames from 'classnames';
import { ThemeProvider, createStyles } from 'antd-style';
import { TabDivider, Tabs } from './Tabs';
import { usePrevious } from 'ahooks';
ThemeProvider;

const log = console.log.bind(null, '[Editor]');

interface Props {
  value: IPlank;
  holeId?: string;
  grooveId?: string;
  onBasisChange: (basis: { x: Basis; y: Basis }) => void;
  onChange(value: IPlank, editTypes: EditType[]): void;
  onSelectHole(holeId: string): void;
  onSelectGroove(grooveId: string): void;
  tabActiveKey?: string;
  onTabChange: GetProp<typeof Tabs, 'onChange'>;
}

export function Editor(props: Props) {
  const { value: plank } = props;
  const [coordinateMatrix, coordinateMatrixInvert] = useMemo(() => {
    const { finishedWidth, finishedHeight, thickness } = plank;
    const matrix = new Matrix4().makeTranslation(
      finishedWidth / 2,
      finishedHeight / 2,
      thickness / 2,
    );
    return [matrix, matrix.clone().invert()];
  }, [plank]);
  const [form] = ProForm.useForm<FormValue>();
  const basis = ProForm.useWatch(['plank', 'basis'], form);
  useEffect(() => {
    // log({basis})
    props.onBasisChange(basis);
  }, [basis?.x, basis?.y]);
  const listLenRecRef = useRef<{
    points: number;
    holes: number;
    groupedGroove: Record<string, number>;
  }>();
  const initialValues = useMemo<FormValue>(() => {
    // log('origin', props.value)
    const {
      finishedWidth,
      finishedHeight,
      thickness,
      finishedProfile,
      holes,
      grooves,
    } = changePlankCoordinateSystem(props.value, coordinateMatrix);
    // log(finishedProfile.points)
    const groupedGroove = defaults(
      groupBy(grooves, 'geometryType'),
      getGroupedGrooveInitValue(),
    );
    // log(groupedGroove)
    const result = {
      plank: {
        size: {
          length: finishedWidth,
          width: finishedHeight,
          thickness,
        },
        basis: defaults(basis, {
          x: Basis.Back,
          y: Basis.Left,
        }),
        isRectPlank: isRectPlank(props.value),
        points: chunk(finishedProfile?.points, 2).map(([x, y]) => ({ x, y })),
        holes,
        groupedGroove,
      },
    };
    listLenRecRef.current = {
      points: result.plank.points.length,
      holes: holes.length,
      groupedGroove: transform(
        groupedGroove,
        (result: Record<string, number>, value, key) => {
          result[key] = value.length;
        },
        {},
      ),
    };
    // log(listLenRecRef.current);
    return result;
  }, [props.value]);
  useEffect(() => {
    form.resetFields();
  }, [initialValues]);

  async function onFinish(formValue: FormValue) {
    const oldValue = changePlankCoordinateSystem(props.value, coordinateMatrix);
    const newValue = cloneDeep(oldValue);
    formValue = form.getFieldsValue(true);
    // log('finish', { formValue, oldValue, newValue });
    // console.log(
    //   form.getFieldsValue(),
    //   form.getFieldsValue(true),
    //   form.getFieldsValue(true, ({ touched }) => touched),
    //   form.isFieldsTouched([
    //     ['plank', 'size'],
    //     ['plank', 'points'],
    //   ]),
    //   form.isFieldsTouched([['plank', 'holes']]),
    //   form.isFieldsTouched([['plank', 'groupedGroove']]),
    //   getEditTypes(form),
    // );

    [handleHoleEdit, handleGrooveEdit, handlePlankEdit].map((handler) => {
      handler(formValue, oldValue, newValue);
    });

    log({
      oldValue,
      newValue,
      result: changePlankCoordinateSystem(newValue, coordinateMatrixInvert),
    });
    props.onChange(
      changePlankCoordinateSystem(newValue, coordinateMatrixInvert),
      getEditTypes(form),
    );
  }
  function handlePlankEdit(
    formValue: FormValue,
    oldValue: IPlank,
    newValue: IPlank,
  ) {
    newValue.finishedWidth = formValue.plank.size.length;
    newValue.finishedHeight = formValue.plank.size.width;
    const halfWidth = oldValue.finishedWidth / 2;
    const halfHeight = oldValue.finishedHeight / 2;
    const basisAxisMap = {
      [Basis.Front]: halfHeight,
      [Basis.Back]: -halfHeight,
      [Basis.Left]: -halfWidth,
      [Basis.Right]: halfWidth,
    };
    const offset = {
      x: newValue.finishedWidth - oldValue.finishedWidth,
      y: newValue.finishedHeight - oldValue.finishedHeight,
    };
    function getInBasisOfPointAt(
      point: {
        x: number;
        y: number;
      },
      basisAxisMap: Record<Basis, number>,
    ): { x: boolean; y: boolean } {
      const basisX = basisAxisMap[formValue.plank.basis.x];
      const basisY = basisAxisMap[formValue.plank.basis.y];
      const inBasis = { x: false, y: false };
      if (point.y === basisX) {
        inBasis.x = true;
      } else if (point.x === basisY) {
        inBasis.y = true;
      }
      return inBasis;
    }
    if (offset.x || offset.y) {
      if (newValue.finishedProfile) {
        newValue.finishedProfile.points = formValue.plank.points
          .map((point) => {
            const inBasis = getInBasisOfPointAt(point, basisAxisMap);
            return {
              x: point.x + (inBasis.y ? 0 : offset.x),
              y: point.y + (inBasis.x ? 0 : offset.y),
            };
          })
          .map((point) => {
            return {
              x: point.x - offset.x / 2,
              y: point.y - offset.y / 2,
            };
          })
          .map(({ x, y }) => [x, y])
          .flat();
      }
      function movePoint(
        point: { x: number; y: number },
        offset: { x: number; y: number },
      ) {
        point.x += offset.x;
        point.y += offset.y;
      }
      function getSign(reverse: boolean) {
        return reverse ? -1 : 1;
      }
      const reverseY = formValue.plank.basis.x === Basis.Front;
      const reverseX = formValue.plank.basis.y === Basis.Right;
      const originOffset = {
        x: (getSign(reverseX) * -offset.x) / 2,
        y: -(getSign(reverseY) * -offset.y) / 2,
      };
      newValue.holes.forEach((hole) => {
        [hole.start, hole.end].forEach((point) => {
          movePoint(point, originOffset);
        });
      });
      newValue.grooves.forEach((groove) => {
        switch (groove.geometryType) {
          case EGrooveGeometryType.SPECIAL_SHAPED:
          case EGrooveGeometryType.PATH:
            const itemHasPoints = groove.profile || groove.path;
            if (itemHasPoints) {
              itemHasPoints.points = chunk(itemHasPoints.points, 2)
                .map(([x, y]) => {
                  const point = { x, y };
                  movePoint(point, originOffset);
                  return [point.x, point.y];
                })
                .flat();
            }
            break;

          case EGrooveGeometryType.SQUARE:
          case EGrooveGeometryType.ROUND_CORNER:
            [groove.start!, groove.end!].forEach((point) => {
              movePoint(point, originOffset);
            });
            break;
        }
      });
    } else {
      if (newValue.finishedProfile) {
        newValue.finishedProfile.points = formValue.plank.points
          .map((point) => {
            return {
              x: point.x,
              y: point.y,
            };
          })
          .map(({ x, y }) => [x, y])
          .flat();
      }
    }
  }
  function handleHoleEdit(
    formValue: FormValue,
    oldValue: IPlank,
    newValue: IPlank,
  ) {
    if (!formValue.plank.holes) return;
    newValue.holes = formValue.plank.holes;
    newValue.holes.forEach((hole) => {
      hole.end = getEndPointByFace({
        face: Number(hole.plankFaceId),
        startPoint: hole.start,
        depth: hole.depth,
      });
    });
  }
  function handleGrooveEdit(
    formValue: FormValue,
    oldValue: IPlank,
    newValue: IPlank,
  ) {
    if (!formValue.plank.groupedGroove) return;
    newValue.grooves = values(formValue.plank.groupedGroove).flat();
  }

  const shouldSubmitRef = useRef<boolean>(false);

  const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
    <DefaultTabBar
      {...props}
      children={(node) => <div className="node-wrap">{node}</div>}
    />
  );

  const [activeKey, setActiveKey] = useState('plank');
  function getGrooveType() {
    const { grooveId } = props;
    if (!grooveId) return;
    const groove = plank.grooves.find((groove) => groove.grooveId === grooveId);
    return groove?.geometryType;
  }
  const onTabChange = (key: string, skipClearSelect = false) => {
    if (key === activeKey) return;
    setActiveKey(key);
    props.onTabChange(key);
    if (skipClearSelect === false) {
      props.onSelectHole('');
      props.onSelectGroove('');
    }
  };
  useEffect(() => {
    if (!props.holeId) return;
    props.onSelectGroove('');
    onTabChange('hole', true);
  }, [props.holeId]);
  useEffect(() => {
    if (!props.grooveId) return;
    const type = getGrooveType();
    if (!type) return;
    // @ts-expect-error
    const key = {
      [EGrooveGeometryType.SQUARE]: 'squareGroove',
      [EGrooveGeometryType.ROUND_CORNER]: 'roundCornerGroove',
    }[type];
    if (!key) return;
    props.onSelectHole('');
    onTabChange(key, true);
  }, [props.grooveId]);

  return (
    <div className={styles['editor']}>
      <ProForm
        initialValues={initialValues}
        form={form}
        layout="horizontal"
        size="small"
        onFinish={onFinish}
        submitter={false}
        colon={false}
        labelCol={{ span: 4 }}
        labelAlign="left"
        onValuesChange={(changedValues, allValues) => {
          shouldSubmitRef.current = true;
          const listLenRec = listLenRecRef.current!;
          const keys = ['points', 'holes'] as const;
          const isAddOrDel =
            values(keys).some((key) => {
              return handleLengthChange(
                pick(listLenRec, keys),
                pick(allValues.plank, keys),
                key,
              );
            }) ||
            (allValues.plank.groupedGroove &&
              values(EGrooveGeometryType).some((type) => {
                return handleLengthChange(
                  listLenRec.groupedGroove,
                  allValues.plank.groupedGroove,
                  type,
                );
              }));
          if (isAddOrDel) {
            form.submit();
          }
        }}
        onBlur={(e) => {
          const el = e.target;
          if (
            el.tagName === 'INPUT' &&
            el.type !== 'radio' &&
            shouldSubmitRef.current
          ) {
            form.submit();
            shouldSubmitRef.current = false;
          }
        }}
      >
        <Tabs
          defaultActiveKey="plank"
          activeKey={activeKey}
          onChange={onTabChange}
          items={[
            {
              key: 'plank',
              className: 'w-60',
              icon: <IconFont type={IconType.Plank} />,
              label: '板件信息',
              children: (
                <LimitHeight>
                  <Block>
                    <PlankInfo plank={props.value} />
                  </Block>
                  <BlockDivider />
                  <Block>
                    <PlankEditor />
                  </Block>
                  <BlockDivider />
                  <Block>
                    <EdgeInfo plank={props.value} />
                  </Block>
                </LimitHeight>
              ),
            },
            TabDivider,
            {
              key: 'hole',
              className: 'w-[464px]',
              icon: <IconFont type={IconType.Hole} />,
              label: '孔',
              children: (
                <LimitHeight>
                  <HoleEditor
                    holeId={props.holeId}
                    onSelectHole={props.onSelectHole}
                  />
                </LimitHeight>
              ),
            },
            {
              key: 'squareGroove',
              className: 'w-[464px]',
              icon: <IconFont type={IconType.SquareGroove} />,
              label: '方槽',
              children: (
                <LimitHeight>
                  <SquareGrooveEditor
                    grooveId={props.grooveId}
                    onSelectGroove={props.onSelectGroove}
                  />
                </LimitHeight>
              ),
            },
            {
              key: 'roundCornerGroove',
              className: 'w-[464px]',
              icon: <IconFont type={IconType.RoundCornerGroove} />,
              label: '圆角方槽',
              children: (
                <LimitHeight>
                  <RoundCornerGrooveEditor
                    grooveId={props.grooveId}
                    onSelectGroove={props.onSelectGroove}
                  />
                </LimitHeight>
              ),
            },
          ]}
        />
      </ProForm>
    </div>
  );
}

function changePlankCoordinateSystem(plank: IPlank, matrix: Matrix4): IPlank {
  plank = cloneDeep(plank);
  if (plank.finishedProfile) {
    plank.finishedProfile.points = chunk(plank.finishedProfile.points, 2)
      .map((point) => {
        return new Vector3()
          .fromArray([...point, 0])
          .applyMatrix4(matrix)
          .toArray()
          .slice(0, 2);
      })
      .flat();
  }
  plank.holes.forEach((hole) => {
    [hole.start, hole.end].map(({ x, y, z }) => {
      return new Vector3(x, y, z).applyMatrix4(matrix);
    });
    hole.start = changePointCoordinateSystem(hole.start, matrix);
    hole.end = changePointCoordinateSystem(hole.end, matrix);
  });
  plank.grooves.forEach((groove) => {
    if (groove.start) {
      groove.start = changePointCoordinateSystem(groove.start, matrix);
    }
    if (groove.end) {
      groove.end = changePointCoordinateSystem(groove.end, matrix);
    }
  });
  return plank;
}

function changePointCoordinateSystem(
  point: PointObj3,
  matrix: Matrix4,
): Number3 {
  return new Vector3(point.x, point.y, point.z).applyMatrix4(matrix);
}
